using System;
using System.Collections.Generic;
using System.IO;

namespace InfiniTec.Threading
{
    /// <summary>
    /// This static class implements often used async operations. 
    /// </summary>
    public static class AsyncOperations
    {
        /// <summary>
        /// Returns an async operation that copies the contents of an input stream to an output stream.
        /// </summary>
        /// <param name="input">The stream to copy the data from.</param>
        /// <param name="output">The stream to copy the data to.</param>
        /// <param name="bytesToCopy">The number of bytes to copy.</param>
        /// <param name="bufferSize">The buffersize for each copy operation.</param>
        /// <returns></returns>
        public static AsyncOperation Copy(Stream input, Stream output, long bytesToCopy, int bufferSize, object userState)
        {
            return new AsyncOperation(CopyInternal(input, output, bytesToCopy, bufferSize), userState);
        }
        /// <summary>
        /// Returns an async operation that copies the contents of an input stream to an output stream.
        /// </summary>
        /// <param name="input">The stream to copy the data from.</param>
        /// <param name="output">The stream to copy the data to.</param>
        /// <param name="bytesToCopy">The number of bytes to copy.</param>
        public static AsyncOperation Copy(Stream input, Stream output, long bytesToCopy, object userState)
        {
            return new AsyncOperation(CopyInternal(input, output, bytesToCopy, 4096), userState);
        }

        /// <summary>
        /// Returns an async operation that copies the contents of an input stream to an output stream.
        /// </summary>
        /// <param name="input">The stream to copy the data from.</param>
        /// <param name="output">The stream to copy the data to.</param>
        public static AsyncOperation Copy(Stream input, Stream output, object userState)
        {
            return new AsyncOperation(CopyInternal(input, output, long.MaxValue, 4096), userState);
        }

        private static IEnumerable<OperationAction> CopyInternal(Stream input, Stream output, long bytesToCopy, int bufferSize)
        {
            byte[] buffer;
            int bytesCopied;
            int count;

            bytesCopied = 0;

            buffer = new byte[Math.Min(bufferSize, bytesToCopy)];

            do
            {
                IAsyncResult ar;
                ar = input.BeginRead(buffer, 0, buffer.Length, null, null);

                yield return new WaitForHandleOperation(ar.AsyncWaitHandle, null, (int) ((double) bytesCopied*100/bytesToCopy*2));

                count = input.EndRead(ar);

                ar = output.BeginWrite(buffer, 0, count, null, null);

                yield return new WaitForHandleOperation(ar.AsyncWaitHandle, null, 50 + (int)((double)bytesCopied * 100 / bytesToCopy * 2));

                output.EndWrite(ar);

                bytesCopied += count;
            } while (count > 0 && bytesCopied < bytesToCopy);

        }

        public static AsyncOperation WrapOperation(string name, AsyncOperation operation, AsyncOperationTracker tracker, bool allowMultipleInvocations,
            object userState)
        {
            return new AsyncOperation(name, WrapInternal(operation), tracker, allowMultipleInvocations, userState);
        }

        public static AsyncOperation<T> WrapOperation<T>(string name, AsyncOperation<T> operation, AsyncOperationTracker tracker, bool allowMultipleInvocations,
            object userState)
        {
            return new AsyncOperation<T>(name, WrapInternal(operation), tracker, allowMultipleInvocations, userState);
        }

        private static IEnumerable<OperationAction> WrapInternal<T>(AsyncOperation<T> operation)
        {
            yield return new NestedOperation(operation);

            yield return new OperationResult<T>(operation.Result);
        }

        private static IEnumerable<OperationAction> WrapInternal(AsyncOperation operation)
        {
            yield return new NestedOperation(operation);
        }
    }
}
